# ~*~ coding: utf-8 ~*~
import json
import shutil
import os
import filetype
import time
import yaml
from flask import request, views, render_template, jsonify, current_app
from flask_login import current_user

from apps.assets.models import Asset
from apps.common.utils import fill_date_to_timestamp, timestamp_obj_to_str, gen_datetime_path, get_signer, short_uuid, \
    get_remote_address
from apps.common.mixin import CompatibleWithSelect2Mixin
from apps.common import response
from apps.common.utils import generate_uuid
from apps.extensions import db
from apps.perms.mixin import PermissionRequiredMixin, LoginRequiredMixin
from apps.tasks.models.realtime.role import RoleExecuteLog
from ...models import Role, InventoryGroup
from ...forms import RoleForm, RoleExecuteForm
from ...tasks import run_role


__all__ = [
    'RoleListAPI', 'RoleListView', 'RoleCreateView', 'RoleUploadView',
    'RoleUpdateView', 'RoleDeleteView', 'RoleDetailView', 'RoleExecuteView',
    'RoleNodeTreeAPI', 'RoleNodeAddChildrenAPI', 'RoleNodeMoveChildrenAPI',
    'RoleNodeDeleteAPI', 'RoleNodeRenameAPI', 'RoleNodeCopyAPI',
    'RoleNodeFileContentAPI', 'RoleNodeFileUploadAPI', 'RoleExecuteLogAPI', 'RoleExecuteLogView'
]


class RoleListAPI(CompatibleWithSelect2Mixin, PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-list'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('create_time', 'gte', start_time),
                ('create_time', 'lte', end_time),
            ],
            'or': [
                # ('name', 'like', request.args.get('search')),
            ],
        }

        # 排序
        sort = request.args.get('sort') or 'create_time'
        order = request.args.get('order') or 'desc'
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        # Compatible with select2
        if request.values.get('type') == 'select2':
            filter_conditions, order_by_list, offset, limit = self.select2_queryset()

        roles, count = Role.get_queryset(filter_conditions=filter_conditions,
                                         order_by_list=order_by_list,
                                         offset=offset, limit=limit)
        results = list()
        for role in roles:
            results.append({
                'id': role.id,
                'name': role.name,
                'code': role.code,
                'desc': role.desc,
                'create_by': role.create_by,
                'create_time': timestamp_obj_to_str(role.create_time),
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class RoleListView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-list'

    def get(self):
        return render_template('tasks/realtime/role_list.html')


class RoleUpdateView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-update'

    def get(self, pk):
        instance = db.session.query(Role).get(pk)
        content = {
            'id': instance.id,
            'name': instance.name,
            'code': instance.code,
            'desc': instance.desc,
            'create_by': instance.create_by,
            'create_time': timestamp_obj_to_str(instance.create_time),
        }
        return render_template('tasks/realtime/role_update.html', **content)

    def post(self, pk):
        form = RoleForm(request.values)
        instance = db.session.query(Role).get(pk)
        if form.validate():
            _clean_data = form.data
            if instance.code != _clean_data.get('code'):
                if db.session.query(Role).filter_by(code=_clean_data.get('code')).count():
                    return response.params_error({
                        'field': 'code',
                        'message': '角色标识不能重复'
                    })

            instance.update(**_clean_data)
            return response.success()
        else:
            return response.params_error(form.get_error())


class RoleCreateView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-create'

    def get(self):
        return render_template('tasks/realtime/role_create.html')

    def post(self):
        form = RoleForm(request.values)
        if form.validate():
            _clean_data = form.data
            if db.session.query(Role).filter_by(code=_clean_data.get('code')).count():
                return response.params_error({
                    'field': 'code',
                    'message': '角色标识不能重复'
                })

            _clean_data['create_by'] = current_user.name
            Role.create(**_clean_data)
            return response.success()
        else:
            return response.params_error(form.get_error())


class RoleDeleteView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-delete'

    def post(self, pk):
        role = db.session.query(Role).get(pk)
        roles_file_path = current_app.config.get('ROLES_FILE_DIR')
        delete_role_path = os.path.join(roles_file_path,  role.code)
        shutil.rmtree(delete_role_path)
        role.delete()
        return response.success()


class RoleDetailView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-detail'

    def get(self, pk):
        instance = db.session.query(Role).get(pk)
        content = {
            'id': instance.id,
            'name': instance.name,
            'desc': instance.desc,
            'code': instance.code,
            'create_by': instance.create_by,
            'create_time': timestamp_obj_to_str(instance.create_time),
        }
        return render_template('tasks/realtime/role_detail.html', **content)


class RoleUploadView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-upload'

    def post(self):
        roles_file_path = current_app.config.get('ROLES_FILE_DIR')
        file = request.files.get('file')
        file_name = file.filename.split('.')[0]
        ext_name = file.filename.split('.')[-1]
        upload_path = os.path.join(roles_file_path, "{}.{}".format(file_name, ext_name))

        db_exists = db.session.query(Role).filter_by(code=file_name).count()
        sys_exists = os.path.exists(upload_path)
        if not db_exists and not sys_exists:
            file.save(upload_path)
            shutil.unpack_archive(upload_path, roles_file_path)
            os.remove(upload_path)
            Role.create(name=file_name, code=file_name)
        return response.success()


class RoleExecuteView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-role-execute'

    def get(self):
        return render_template('tasks/realtime/role_execute.html')

    def post(self):
        form = RoleExecuteForm(request.values)
        if form.validate():
            _clean_data = form.data
            host_ids = _clean_data.get('hosts').split(',')
            group_ids = _clean_data.get('groups').split(',')
            role_ids = _clean_data.get('roles').split(',')
            groups = db.session.query(InventoryGroup).filter(InventoryGroup.id.in_(group_ids)).all()
            hosts = db.session.query(Asset).filter(Asset.id.in_(host_ids)).all()
            roles = db.session.query(Role).filter(Role.id.in_(role_ids)).all()

            host_names = [h.hostname for h in hosts]
            group_names = [g.name for g in groups]
            host_names.extend(group_names)

            role_codes = []
            for role_id in role_ids:
                for role in roles:
                    if role.id == role_id:
                        role_codes.append(role.code)
                        break
            site = [
                {
                    'hosts': ','.join(host_names),
                    'gather_facts': 'yes',
                    'roles': role_codes
                }
            ]

            uid = generate_uuid()
            exec_name = '_exec_role_{}'.format(uid)
            exec_path = os.path.join(current_app.config.get('TASKS_BASE').rstrip('/'), exec_name)
            if not os.path.exists(exec_path):
                os.makedirs(exec_path)

            site_path = os.path.join(exec_path, 'site.yaml')
            with open(site_path, 'w+', encoding='utf-8') as f:
                f.write(yaml.dump(site))
                os.chmod(site_path, 0o400)

                for code in role_codes:
                    role_path = os.path.join(current_app.config.get('ROLES_FILE_DIR'), code)
                    shutil.copytree(role_path, os.path.join(exec_path, code))

            log = {
                'user': "{} ({})".format(current_user.name, current_user.username),
                'remote_ip': get_remote_address(),
                'playbook': json.dumps(site),
                'hosts': ','.join(host_names),
                'groups': ','.join(group_names),
                'codes': ','.join(role_codes),
                'exec_time': time.time(),
            }
            log = RoleExecuteLog.create(**log)

            # run roles
            task = run_role.apply_async(args=(hosts, groups, site_path, log), serializer='pickle')
            return response.success(data={'task_id': task.id})
        else:
            return response.params_error(form.get_error())


# Log

class RoleExecuteLogAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-log-role'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('exec_time', 'gte', start_time),
                ('exec_time', 'lte', end_time),
            ],
            'or': [
                ('name', 'like', request.args.get('search')),
            ],
        }

        # 排序
        sort = request.args.get('sort') or 'create_time'
        order = request.args.get('order') or 'desc'
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        logs, count = RoleExecuteLog.get_queryset(filter_conditions=filter_conditions,
                                                  order_by_list=order_by_list,
                                                  offset=offset, limit=limit)
        results = list()
        for log in logs:
            results.append({
                'id': log.id,
                'celery_id': log.celery_id,
                'user': log.user,
                'remote_ip': log.remote_ip,
                'playbook': log.playbook,
                'hosts': log.hosts,
                'groups': log.groups,
                'codes': log.codes,
                'result': json.dumps(log.result),
                'exec_time': timestamp_obj_to_str(log.exec_time),
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class RoleExecuteLogView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-log-role'

    def get(self):
        return render_template('tasks/realtime/role_log.html')


# Node


class OpSys:

    @staticmethod
    def list(base_path):
        signer = get_signer()
        directory_cmd = """ls -l %s| grep '^d'|awk '{print $NF}'""" % base_path
        file_cmd = """ls -l  %s| grep '^-'|awk '{print $NF}'""" % base_path

        all_directory = os.popen(directory_cmd).read().split('\n')[0:-1]
        all_file = os.popen(file_cmd).read().split('\n')[0:-1]

        data = []
        for dir_name in all_directory:
            uid = generate_uuid()
            node_path = os.path.join(base_path, dir_name)
            data.append({
                'id': uid,
                'name': dir_name,
                'pId': None,
                'current': signer.sign(node_path),
                'parent': signer.sign(base_path),
                'isParent': True,  # 目录为True, 文件为False
                'open': False,
                'meta': {
                    'node': {
                        'key': uid,
                        'value': dir_name,
                        'current': signer.sign(node_path),
                        'parent': signer.sign(base_path),
                        'isParent': True,
                    },
                    'type': 'node'
                }
            })

        for file_name in all_file:
            uid = generate_uuid()
            node_path = os.path.join(base_path, file_name)
            data.append({
                'id': uid,
                'name': file_name,
                'pId': None,
                'current': signer.sign(node_path),
                'parent': signer.sign(base_path),
                'isParent': False,  # 目录为True, 文件为False
                'open': False,
                'meta': {
                    'node': {
                        'key': uid,
                        'value': file_name,
                        'current': signer.sign(node_path),
                        'parent': signer.sign(base_path),
                        'isParent': False,
                    },
                    'type': 'node'
                }
            })
        return data

    @staticmethod
    def mkdir(base_path, node_type):
        signer = get_signer()

        if not os.path.isdir(base_path):
            data = {
                'message': '该节点不是目录，无法新建！{}'.format(base_path)
            }
            return False, data
        if node_type == 'dir':
            name = 'NewFolder-{}'.format(short_uuid())
            new_path = os.path.join(base_path, name)
            os.mkdir(new_path)
            is_parent = True
        elif node_type == 'file':
            name = 'NewFile-{}'.format(short_uuid())
            new_path = os.path.join(base_path, name)
            os.popen(cmd='touch {}'.format(new_path))
            is_parent = False
        else:
            data = {
                'message': '不支持的操作类型，无法新建！'
            }
            return False, data

        uid = generate_uuid()
        data = {
            'id': uid,
            'name': name,
            'pId': None,
            'current': signer.sign(new_path),
            'parent': signer.sign(base_path),
            'isParent': is_parent,  # 目录为True, 文件为False
            'open': False,
            'meta': {
                'node': {
                    'key': uid,
                    'value': name,
                    'current': signer.sign(new_path),
                    'parent': signer.sign(base_path),
                    'isParent': is_parent
                },
                'type': 'node'
            }
        }
        return True, data

    @staticmethod
    def rename(base_path, old_node, new_node):
        signer = get_signer()
        old_node_path = os.path.join(base_path, old_node)
        new_node_path = os.path.join(base_path, new_node)

        if os.path.exists(new_node_path):
            data = {
                'message': '该节点名称已存在，请修改！'
            }
            return False, data

        os.rename(old_node_path, new_node_path)
        data = {
            'name': new_node,
            'current': signer.sign(new_node_path),
            'parent': signer.sign(base_path),
            'meta': {
                'node': {
                    'value': new_node,
                    'current': signer.sign(new_node_path),
                    'parent': signer.sign(base_path),
                },
                'type': 'node'
            }
        }
        return True, data

    @staticmethod
    def remove(path):
        if os.path.isfile(path):
            os.remove(path)
        if os.path.isdir(path):
            shutil.rmtree(path)
        return True, {}

    @staticmethod
    def read_file(path):
        if not os.path.isfile(path):
            data = {
                'message': '请求的不是文件！'
            }
            return False, data

        with open(path, 'r') as f:
            data = f.read()
        return True, data

    @staticmethod
    def write_file(path, content):
        if not os.path.isfile(path):
            data = {
                'message': '请求的不是文件！'
            }
            return False, data
        with open(path, 'w') as f:
            data = f.write(content)
        return True, data

    @staticmethod
    def upload_file(file, path, parent_id):
        signer = get_signer()
        file_name = file.filename
        file_path = os.path.join(path, file_name)
        file.save(file_path)

        uid = short_uuid()
        data = {
            'id': uid,
            'name': file_name,
            'pId': parent_id,
            'current': signer.sign(file_path),
            'parent': signer.sign(path),
            'isParent': False,  # 目录为True, 文件为False
            'open': False,
            'meta': {
                'node': {
                    'key': uid,
                    'value': file_name,
                    'current': signer.sign(file_path),
                    'parent': signer.sign(path),
                    'isParent': False
                },
                'type': 'node'
            }
        }
        return True, data


class RoleNodeTreeAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-tree'

    def has_perm(self):
        return True

    def get(self):
        role_id = request.args.get('role_id')
        role = db.session.query(Role).filter_by(id=role_id).one()
        roles_file_path = current_app.config.get('ROLES_FILE_DIR')
        target_path = os.path.join(roles_file_path, role.code)

        name = request.args.get('name')
        parent_path = request.args.get('parent')
        if name and parent_path:
            parent_path = get_signer().unsign(parent_path)
            target_path = os.path.join(parent_path, name)

        results = OpSys.list(target_path)
        return jsonify(results)


class RoleNodeAddChildrenAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-add'

    def has_perm(self):
        return True

    def post(self, pk):
        current_node = get_signer().unsign(request.args.get('node_id'))
        node_type = request.form.get('type')
        results = OpSys.mkdir(current_node, node_type)
        is_success, data = results[0], results[1],
        if is_success:
            return response.success(data=data)
        else:
            return response.params_error(data=data)


class RoleNodeRenameAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-rename'

    def has_perm(self):
        return True

    def post(self, pk):
        new_node = request.form.get('node_name')
        parent_id = request.form.get('parent')
        parent_path = get_signer().unsign(parent_id)
        current_node = request.args.get('node_id')
        old_node = get_signer().unsign(current_node).split('/')[-1]

        results = OpSys.rename(parent_path, old_node, new_node)
        is_success, data = results[0], results[1],
        if is_success:
            return response.success(data=data)
        else:
            return response.params_error(data=data)


class RoleNodeMoveChildrenAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-move'

    def has_perm(self):
        return True

    def post(self, pk):
        return response.params_error(data={'message': '不支持该操作'})


class RoleNodeDeleteAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-delete'

    def has_perm(self):
        return True

    def post(self, pk):
        node_id = request.args.get('node_id')
        current_node = get_signer().unsign(node_id)
        results = OpSys.remove(current_node)
        is_success, data = results[0], results[1],
        if is_success:
            return response.success(data=data)
        else:
            return response.params_error(data=data)


class RoleNodeCopyAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-copy'

    def has_perm(self):
        return True

    def post(self, pk):
        return response.params_error(data={'message': '不支持该操作'})


class RoleNodeFileContentAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-file'

    NOT_SUPPORT_TYPE = (
        'application/x-bzip2', 'application/gzip', 'application/x-tar',
        'application/zip', 'application/x-7z-compressed', 'application/x-rpm'
    )

    def has_perm(self):
        return True

    @classmethod
    def validation(cls, current_node):
        kind = filetype.guess(current_node)
        if kind and kind.mime in cls.NOT_SUPPORT_TYPE:
            return False

        return True

    def get(self, pk):
        node_id = request.args.get('node_id')
        current_node = get_signer().unsign(node_id)
        name = current_node.split('/')[-1]

        if not self.validation(current_node):
            data = {'message': 'Not Support'}
            return response.params_error(data=data)
        is_success, data = OpSys.read_file(current_node)

        if is_success:
            data = {
                'name': name,
                'content': data,
            }
            return response.success(data=data)
        else:
            return response.params_error(data=data)

    def post(self, pk):
        node_id = request.args.get('node_id')
        current_node = get_signer().unsign(node_id)
        content = request.form.get('content')

        if not self.validation(current_node):
            data = {'message': 'Not Support'}
            return response.params_error(data=data)
        is_success, data = OpSys.write_file(current_node, content)

        if is_success:
            return response.success(data={})
        else:
            return response.params_error(data=data)


class RoleNodeFileUploadAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'role-node-file'

    def has_perm(self):
        return True

    def post(self, pk):
        file = request.files.get('file')
        parent_id = request.form.get('node_id')
        parent_node = request.form.get('current_node')
        parent_node = get_signer().unsign(parent_node)

        results = OpSys.upload_file(file, parent_node, parent_id)

        is_success, data = results[0], results[1],
        if is_success:
            return response.success(data=data)
        else:
            return response.params_error(data=data)
